#!/usr/bin/env bash
# Launcher script for Kontalk Docker server
# Inspired by the Discourse launcher

set -e

usage () {
  echo "Usage: launcher COMMAND"
  echo "Commands:"
  echo "    start:      Start/initialize containers"
  echo "    stop:       Stop running containers"
  echo "    restart:    Restart containers"
  echo "    destroy:    Stop and remove containers"
  echo "    enter:      Open a shell to run commands inside the xmpp container"
  echo "    logs:       View the Docker logs for containers"
  echo "    bootstrap:  Bootstrap containers for the config"
  echo "    rebuild:    Rebuild containers (destroy old, bootstrap, start new)"
  echo "    backup:     Initiate server backup procedure"
  echo "    restore:    Restore a previously generated backup"
  echo
  echo "Any other command will be passed through to docker-compose, including all arguments."
  exit 1
}

command=$1

if [ -z "$command" ]; then
  usage
fi

cd "$(dirname "$0")"

docker_path=`which docker.io || which docker`
dockercompose_path=`which docker-compose`
gpg_path=`which gpg2 || which gpg`
config_file=local.properties

# escape sequences for output
set +e
bold=$(tput bold 2>/dev/null || false)
normal=$(tput sgr0 2>/dev/null || false)
set -e

install_docker() {
  echo "Docker is not installed, you will need to install Docker in order to run Kontalk"
  echo "See https://docs.docker.com/installation/"
  exit 1
}

install_dockercompose() {
  echo "Docker Compose is not installed, you will need to install Docker Compose in order to run Kontalk"
  echo "See https://docs.docker.com/compose/install/"
  exit 1
}

run_setup_first() {
    echo "Run \`./kontalk-setup\` first."
    exit 1
}

check_prereqs() {
    if [ -z ${docker_path} ]; then
        install_docker
    fi
    if [ -z ${dockercompose_path} ]; then
        install_dockercompose
    fi
    if [ ! -f ${config_file} ]; then
        run_setup_first
    fi
}

check_version() {
    if [ ! -f .version ]; then
        echo "Run \`./launcher bootstrap\` first."
        exit 1
    fi
}

# check prerequisites
check_prereqs

# load configuration
set -a
. local.properties
set +a

DATADIR=data
CONFDIR=config
SSL_TRUSTED=trusted.pem
TIGASE_CONF=init.properties.in
TIGASE_ENV=tigase.conf
HTTUPLOAD_CONF=config.yml.in
REPO_URL=https://github.com/kontalk/tigase-kontalk.git

docker_compose() {
    check_version
    (export IMAGE_VERSION=$(cat .version); set -x; ${dockercompose_path} -p ${INSTANCE_NAME} $*)
}

docker_compose_internal() {
    check_version
    (export IMAGE_VERSION=$(cat .version); ${dockercompose_path} -p ${INSTANCE_NAME} $*)
}

docker_compose_exec() {
    check_version
    export IMAGE_VERSION=$(cat .version); set -x; exec ${dockercompose_path} -p ${INSTANCE_NAME} $*
}

monitor_docker_build() {
    image="$1"
    base="$2"
    shift 2

    if [ -n "${DEBUG}" ]; then
        ${docker_path} build -t ${image} $* ${base}
    else
        ${docker_path} build -t ${image} $* ${base} >/dev/null &
        docker_pid=$!

        trap "kill ${docker_pid} 2>/dev/null" EXIT

        spin[0]="-"
        spin[1]="\\"
        spin[2]="|"
        spin[3]="/"

        echo -n "${image} ${spin[0]}"
        while kill -0 ${docker_pid} 2>/dev/null
        do
            for i in "${spin[@]}"
            do
                echo -ne "\b$i"
                sleep 0.1
            done
        done

        trap - EXIT
        wait ${docker_pid} && echo -e "\bOK"
    fi
}

run_bootstrap() {
    # get information about what we are going to build
    build=${VERSION}
    serverbuild=${VERSION}
    docker_tag=$(echo ${VERSION} | colrm 8)

    refs=$(git ls-remote ${REPO_URL} ${VERSION})
    commit=$(echo "${refs}" | cut -f 1 -)
    ref=$(echo "${refs}" | cut -f 2 -)

    echo ${ref} | grep heads >/dev/null &&
        docker_tag=$(echo ${commit} | colrm 8) &&
        build=${commit} &&
        echo "${bold}Building images for branch ${VERSION} (${commit})${normal}"
    echo ${ref} | grep tags >/dev/null &&
        docker_tag=${build} &&
        echo "${bold}Building images for version ${VERSION}${normal}"

    # build images
    echo "This could take several minutes."
    docker pull kontalk/xmppserver:${docker_tag} || monitor_docker_build kontalk/xmppserver:${docker_tag} tigase --build-arg BRANCH=${build} --build-arg SERVER_BRANCH=${serverbuild}
    monitor_docker_build kontalk/httpupload httpupload
    echo

    # ensure config directory exists
    mkdir -p ${CONFDIR}

    # check GPG key
    if [ ! -f ${CONFDIR}/server-private.key ] || [ ! -f ${CONFDIR}/server-public.key ]; then
        echo "${bold}Not using provided GPG server key, I'll generate one later automatically.${normal}"
        echo "You can provide a GPG server key by exporting it into ${CONFDIR}/server-private.key and ${CONFDIR}/server-public.key"
        echo "Please note that the private key MUST NOT be password-protected."
        echo
    fi

    # check if private GPG key is not password-protected
    if [ -f ${CONFDIR}/server-private.key ] && ${gpg_path} --list-packets ${CONFDIR}/server-private.key | grep protected &>/dev/null; then
        echo "${bold}The provided GPG server key is password-protected.${normal}"
        echo "That can't be used in an automated system. Please remove the passphrase from the private key using GnuPG and export it again."
        exit 1
    fi

    # check certificate
    if [ ! -f ${CONFDIR}/privatekey.pem ] || [ ! -f ${CONFDIR}/certificate.pem ]; then
        echo "${bold}Not using provided X.509 certificate, I'll generate one later automatically.${normal}"
        echo "If you want to provide an existing X.509 certificate for the server,"
        echo "you can copy it into ${CONFDIR}/privatekey.pem and ${CONFDIR}/certificate.pem"
        echo "An optional CA chain can be provided into ${CONFDIR}/cachain.pem"
        echo
    fi

    # check trusted.pem
    if [ ! -f ${CONFDIR}/${SSL_TRUSTED} ];
    then
        # copy default trusted certs bundle
        echo "Using default trusted certs bundle"
        cp ${DATADIR}/${SSL_TRUSTED}.dist ${CONFDIR}/${SSL_TRUSTED}
        echo
    fi

    # check init.properties
    if [ ! -f ${CONFDIR}/${TIGASE_CONF} ];
    then
        echo "Using default Tigase configuration"
        cp ${DATADIR}/${TIGASE_CONF}.dist ${CONFDIR}/${TIGASE_CONF}
        echo
    fi

    if [ ! -f ${CONFDIR}/${TIGASE_ENV} ];
    then
        echo "Using default Tigase environment"
        cp ${DATADIR}/${TIGASE_ENV}.dist ${CONFDIR}/${TIGASE_ENV}
        echo
    fi

    # check config.yml (httpupload)
    if [ ! -f ${CONFDIR}/${HTTUPLOAD_CONF} ];
    then
        echo "Using default HTTP upload component configuration"
        cp ${DATADIR}/${HTTUPLOAD_CONF}.dist ${CONFDIR}/${HTTUPLOAD_CONF}
        echo
    fi

    # trusted keystore
    rm -f ${CONFDIR}/truststore
    if [ -d trustedcerts ]; then
        for i in trustedcerts/*.cer; do
            keytool -importcert -keystore ${CONFDIR}/truststore -storepass truststore -noprompt -alias $(basename ${i} .cer) -file ${i} &>/dev/null
        done
    fi

    echo ${docker_tag} > .version
}

run_start() {
    docker_compose up -d $*
}

run_stop() {
    docker_compose stop -t 10 $*
}

run_backup() {
    check_version

    if [[ ! -d "${BACKUP_PATH}" ]]; then
        echo "Backup path not configured or not a directory. Please configure an accessible directory in the BACKUP_PATH configuration key in local.properties."
        exit 1
    fi

    # create temporary directory
    OUTDIR="${BACKUP_PATH}/${INSTANCE_NAME}-`date +%Y-%m-%d_%H-%M-%S`"
    mkdir -p "${OUTDIR}/db"
    mkdir -p "${OUTDIR}/httpupload"
    mkdir -p "${OUTDIR}/xmpp"

    # dump the database
    echo "${bold}Dumping database${normal}"
    # TODO fallback to an emergency container if original fails
    if ! docker_compose exec -T db /usr/bin/mysqldump \
            --skip-opt \
            --single-transaction \
            --quick \
            --create-options \
            --extended-insert \
            --routines \
            --set-charset \
            --no-autocommit \
            --user=kontalk --password="${MYSQL_PASSWORD}" \
            kontalk >"${OUTDIR}/db/kontalk.sql" ; then
        echo
        echo -e "${bold}Unable to backup database. Ensure the database container is running by launching: \`./launcher start db\`${normal}" &&
        exit 1
    fi

    # copy httpupload files
    # not really transactional, but we can live with it
    echo "${bold}Dumping httpupload files${normal}"
    docker run --rm -v "${INSTANCE_NAME}_httpupload_data:/out_data" \
        busybox tar -C /out_data -cvf - . >"${OUTDIR}/httpupload/disk.tar"

    # copy xmpp files
    # TODO create kyoto cabinet tools image based on busybox so we can use kchashmgr copy
    echo "${bold}Dumping keyring${normal}"
    docker run --rm -v "${INSTANCE_NAME}_xmpp_data:/out_data" \
        busybox cat /out_data/keyring.kch >"${OUTDIR}/xmpp/keyring.kch"

    # create the backup file (intentionally manually crafted list of files)
    echo "${bold}Creating backup archive${normal}"
    OUTFILE="${OUTDIR}.tar.gz"
    tar -C "${OUTDIR}" -cvzf "${OUTFILE}" \
        "db/" \
        "httpupload/" \
        "xmpp/"
    rm -fR "${OUTDIR}"

    echo
    echo "${bold}Backup created: ${OUTFILE}${normal}"
}

run_restore() {
    check_version

    RESTORE_FILE="$1"
    # TODO if $1 is empty, take latest (by name) file in BACKUP_PATH/${INSTANCE_NAME}_*.tar.gz
    if [[ "${RESTORE_FILE}" == "" ]]; then
        echo "Usage: launcher restore <backup_file.tar.gz>"
        exit 1
    fi
    if [[ ! -f "${RESTORE_FILE}" ]]; then
        echo "Unable to read backup file ${RESTORE_FILE}"
        exit 1
    fi

    # ask for confirmation
    echo "${bold}You are about to restore the backup from:"
    echo "  ${RESTORE_FILE}"
    echo
    echo "Any existing containers and their data in instance '${INSTANCE_NAME}' WILL BE DESTROYED.${normal}"
    echo
    read -p "Type 'OK' to proceed: " restore_ack
    [[ "${restore_ack}" == "OK" ]] || exit 1

    echo
    # take down the whole stack and re-create it
    echo "${bold}Rebuilding all containers and data${normal}"
    docker_compose down -v
    docker_compose up --no-start

    # restore database
    echo
    echo "${bold}Restoring database${normal}"

    # wait for the database to spin up
    docker_compose up -d db
    while ! docker_compose exec db mysql -u kontalk -p"${MYSQL_PASSWORD}" kontalk -e 'status' &>/dev/null ; do
        echo "Waiting for database connection..."
        sleep 4
    done

    tar -xOzf "${RESTORE_FILE}" "db/kontalk.sql" |
        docker exec -i $(docker_compose_internal ps -q db) \
            mysql -u kontalk -p"${MYSQL_PASSWORD}" kontalk

    # restore httpupload files
    echo
    echo "${bold}Restoring httpupload files${normal}"
    docker_compose up -d httpupload

    tar -xOzf "${RESTORE_FILE}" "httpupload/disk.tar" |
        docker exec -i $(docker_compose_internal ps -q httpupload) \
            tar -C /home/kontalk/disk -xf -

    # restore xmpp data
    echo
    echo "${bold}Restoring keyring${normal}"
    docker_compose up -d xmpp

    tar -xOzf "${RESTORE_FILE}" "xmpp/keyring.kch" |
        docker exec -i $(docker_compose_internal ps -q xmpp) \
            tee /home/kontalk/data/keyring.kch >/dev/null

    echo
    echo "${bold}Server should now be up and running.${normal}"
}

case "$command" in
    bootstrap)
        run_bootstrap
        echo "${bold}Successfully bootstrapped, to startup use \`./launcher start\`${normal}"
        exit 0
        ;;

    start)
        shift 1
        run_start $*
        exit 0
        ;;

    stop)
        shift 1
        run_stop $*
        exit 0
        ;;

    restart)
        shift 1
        run_stop $*
        run_start $*
        exit 0
        ;;

    rebuild)
        echo "${bold}Stopping and removing old containers${normal}"
        docker_compose down

        run_bootstrap
        run_start
        exit 0
        ;;

    destroy)
        docker_compose down
        exit 0
        ;;

    backup)
        run_backup
        exit 0
        ;;

    restore)
        shift 1
        run_restore $*
        exit 0
        ;;

    enter)
        docker_compose_exec exec xmpp /bin/bash --login
        ;;

    root)
        docker_compose_exec exec --privileged --user root xmpp /bin/bash --login
        ;;

    *)
        shift 1
        docker_compose_exec ${command} $*
        ;;

esac

usage
